package com.zx.crowdfunding.mvc.config;

import java.util.ArrayList;
import java.util.List;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.vote.AffirmativeBased;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.session.ExpiringSession;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.security.SpringSessionBackedSessionRegistry;

import com.zx.crowdfunding.security.CrowdFundingAccessDecisionVoter;
import com.zx.crowdfunding.security.CrowdFundingAccessDeniedHandler;
import com.zx.crowdfunding.security.CrowdFundingFilterInvocationSecurityMetadataSource;
import com.zx.crowdfunding.security.CrowdFundingFilterSecurityInterceptor;
import com.zx.crowdfunding.security.CrowdFundingUserDetailsService;

/**
 * SpringSecurity配置类
 * @author 郑雪
 * @date 2021-12-11
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class WebAppSecurityConfig extends WebSecurityConfigurerAdapter {
	
	/**
	 * 认证
	 */
	@Override
	protected void configure(AuthenticationManagerBuilder auth)
			throws Exception {
		
		// 配置使用自定义的UserDetailsService接口实现类来进行用户的认证，并使用密码加密（即登录）
		auth.userDetailsService(myUserDetailsService()).passwordEncoder(passwordEncoder());
	}

	/**
	 * 授权
	 */
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		
		http
			.addFilterBefore(								// 添加过滤器
					myFilterSecurityInterceptor(), 			// 自定义的权限鉴权拦截器 
					FilterSecurityInterceptor.class)		// 加到FilterSecurityInterceptor过滤器之前
			.authorizeRequests()							// 对请求进行授权
			.antMatchers("/admin/to/login/page.html")		// 登录页面的请求
			.permitAll()               						// 放行
			.antMatchers("/bootstrap/**")					// 静态资源的请求
			.permitAll()									// 放行
			.antMatchers("/crowdfunding/**")				// 静态资源的请求
			.permitAll()									// 放行
			.antMatchers("/css/**")							// 静态资源的请求
			.permitAll()									// 放行
			.antMatchers("/fonts/**")						// 静态资源的请求
			.permitAll()									// 放行
			.antMatchers("/img/**")							// 静态资源的请求
			.permitAll()									// 放行
			.antMatchers("/jquery/**")						// 静态资源的请求
			.permitAll()									// 放行
			.antMatchers("/layer/**")						// 静态资源的请求
			.permitAll()									// 放行
			.antMatchers("/script/**")						// 静态资源的请求
			.permitAll()									// 放行
			.antMatchers("/ztree/**")						// 静态资源的请求
			.permitAll()									// 放行
			//@PreAuthorize("hasRole('ROLE_USER') and 'zhangsan' == authentication.principal.username")
			//.antMatchers("/admin/get/page.html").access("hasRole('ROLE_USER') and 'zhangsan' == authentication.principal.username")
			.anyRequest()              						// 其他任意请求
			.authenticated()           						// 认证后才能访问
			.and()
			.formLogin()                                    // 使用自定义的登录表单
			.loginPage("/admin/to/login/page.html")			// 登录页面的地址
			.loginProcessingUrl("/security/do/login.html")	// 提交登录表单的地址
			.defaultSuccessUrl("/admin/to/main/page.html")	// 登录成功地址
			.usernameParameter("loginAcct")					// 登录账号的请求参数名
			.passwordParameter("userPswd")					// 登录密码的请求参数名
			.and()
			.logout()										// 退出登录
			.logoutUrl("/security/do/logout.html")			// 退出登录的地址
			.logoutSuccessUrl("/admin/to/login/page.html")	// 退出登录成功跳转地址
			.logoutRequestMatcher(							// 退出登录请求方式改为GET
				new AntPathRequestMatcher(
					"/security/do/logout.html", 
					"GET"
				)
			)
			.and()
			.exceptionHandling()							// 异常处理
			.accessDeniedHandler(myAccessDeniedHandler())	// 访问被拒绝的异常处理
			.and()
			.sessionManagement()							// Session管理器
			.maximumSessions(1)								// Session最大并发数量（同一个账号，最多只有一个会话，踢出上一个会话）
			.sessionRegistry(sessionRegistry())				// 使用SpringSessionJDBC整合SpringSecurity，SpringSecurity默认是将Session存在内存中的。
//			.expiredUrl(expiredUrl)							// Session失效后的跳转地址
//			.expiredSessionStrategy(expiredSessionStrategy)	// Session在并发下失效后的处理策略
						
			
			;
	}
	
	/**
	 * 使用SpringSession整合SpringSecurity
	 * @return
	 */
	@SuppressWarnings("unchecked")
	@Bean
	public SessionRegistry sessionRegistry(){
		ApplicationContext applicationContext = getApplicationContext();
		FindByIndexNameSessionRepository<ExpiringSession> sessionRepository = (FindByIndexNameSessionRepository<ExpiringSession>) applicationContext.getBean("sessionRepository");
		return new SpringSessionBackedSessionRegistry(sessionRepository);
	}
	
	/**
	 * 自定义的权限鉴权拦截器
	 */
	@Bean
	public CrowdFundingFilterSecurityInterceptor myFilterSecurityInterceptor(){
		// 创建一个自定义的权限鉴权拦截器
		CrowdFundingFilterSecurityInterceptor myFilterSecurityInterceptor = new CrowdFundingFilterSecurityInterceptor();
		// 配置自定义的资源权限数据源
		myFilterSecurityInterceptor.setSecurityMetadataSource(mySecurityMetadataSource());
		// 配置SpringSecurity默认的访问决策管理器AffirmativeBased，并使用投票者WebExpressionVoter
		myFilterSecurityInterceptor.setAccessDecisionManager(myAccessDecisionManager());
		return myFilterSecurityInterceptor;
	}
	
	/**
	 * 自定义的资源授权的数据源
	 */
	@Bean
	public CrowdFundingFilterInvocationSecurityMetadataSource mySecurityMetadataSource() {
		return new CrowdFundingFilterInvocationSecurityMetadataSource(null);
	}
	
	/**
	 * 自定义的访问决策管理器
	 */
	@Bean
	public AccessDecisionManager myAccessDecisionManager(){
		List<AccessDecisionVoter<? extends Object>> voters = new ArrayList<>();
		voters.add(myAccessDecisionVoter());
		return new AffirmativeBased(voters);
	}
	
	/**
	 * 自定义的访问决策投票者
	 */
	@Bean
	public AccessDecisionVoter<? extends Object> myAccessDecisionVoter(){
		return new CrowdFundingAccessDecisionVoter();
	}

	/**
	 * 自定义的UserDetailsService实现类
	 */
	@Bean
	public UserDetailsService myUserDetailsService(){
		return new CrowdFundingUserDetailsService();
	}
	
	/**
	 * 自定义的访问被拒绝的异常处理器（403）
	 */
	@Bean
	public AccessDeniedHandler myAccessDeniedHandler(){
		return new CrowdFundingAccessDeniedHandler();
	}
	
	/**
	 * 密码编码器
	 */
	@Bean
	public PasswordEncoder passwordEncoder(){
		// 这是一个SpringSecurity里自带的一个密码编辑器
		// 它的好处是：随机盐值（同一个密码每次加密后的值都不一样）
		return new BCryptPasswordEncoder();
	}
	
	/**
	 * 设置语言包
	 */
	@Bean
    public ReloadableResourceBundleMessageSource messageSource(){
        ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
        //加载org/springframework/security包下的中文提示信息 配置文件
        messageSource.setBasename("classpath:messages_zh_CN");
        return messageSource;
    }
	
}
